<script lang="ts">
    import { untrack } from "svelte";
    import { useClassList } from "../utils/useClassList";
    import type { SelectProps } from "./Select";
    import type { TagConfig } from "../TagGroup";
    import { Dropdown } from "../Dropdown";
    import Icon from "../Common/Icons/Icon.svelte";
    import Option from "./Option.svelte";
    import EmptyOption from "./EmptyOption.svelte";
    import { Value } from "../Common/Value";
    import { Spin } from "../Spin";
    import { VirtualList } from "../VirtualList";
    import { useFormItemContext } from "../FormItem/FormItem.svelte";
    import { Empty } from "../Empty";

    let { multi = false, value = $bindable(multi ? [] : undefined), ...props }: SelectProps = $props();
    const ctx = useFormItemContext();
    let wrap: any;
    const textField = props.textField || "label";
    const valueField = props.valueField || "value";
    let open = $state(false);
    const align = props.align ?? "bottomLeft";

    const allTextNodes: Node[] = [];
    let filterWrap: any;

    let initLabels: any[] = [];
    if (props.filter && props.defaultLabel) {
        if (multi && props.defaultLabel instanceof Array) {
            props.defaultLabel.forEach((label: string, index: number) => {
                initLabels.push({ [valueField]: value[index], [textField]: label });
            });
        } else {
            initLabels = [{ [valueField]: value, [textField]: props.defaultLabel }];
        }
    }
    let isClickChanging = true;
    let query = $state("");
    let showLabels = $state(initLabels);
    let debounceTimer: any = null;
    let dataMap: any = {};
    const store = $state({
        list: [] as any[],
    });

    // 当单选且有默认label时先禁止查询，后放开
    queueMicrotask(() => {
        isClickChanging = false;
    });

    const classList = $derived(
        useClassList(props, "cm-select", {
            "cm-select-disabled": props.disabled,
            [`cm-select-${props.size}`]: props.size,
            "cm-select-clearable": !props.disabled && props.clearable && `${value}`.length !== 0,
            "cm-select-multi": multi,
            "cm-select-open": open,
            "cm-select-with-prefix": props.prefix,
            [`cm-select-status-${props.status}`]: props.status,
        })
    );

    // 重新构建数据
    function buildData(arr: any[], target: any[]) {
        arr &&
            arr.forEach((item) => {
                target.push(item);
                item._show = true;
                dataMap[item[valueField]] = item;
                if (item.children && item.children.length) {
                    item.group = true;
                    buildData(item.children, target);
                }
            });
    }

    // 传入的data变化同步更新
    const newData = $derived.by(() => {
        const data = props.data || [];
        dataMap = {};
        const newData: any[] = [];
        if (props.emptyOption) {
            newData.push({ [valueField]: "", [textField]: props.emptyOption, _show: true, emptyOption: true });
        }
        if (initLabels) {
            initLabels.forEach((label) => {
                newData.push({ ...label, _show: true });
            });
        }
        if (data) {
            untrack(() => {
                buildData(data, newData);
            });
        }
        return newData;
    });

    $effect(() => {
        const val = untrack(() => value);
        store.list = newData;
        const labels: string[] = untrack(() => showLabels || []);
        const labelMap: any = {};
        labels.forEach((label: any) => {
            labelMap[label[valueField]] = label;
        });
        untrack(() => {
            store.list.forEach((item) => {
                if (multi) {
                    item._checked = val.includes(item[valueField]);
                } else {
                    item._checked = val === item[valueField];
                }
                if (item._checked) {
                    if (!labelMap[item[valueField]]) {
                        labels.push(item);
                    } else {
                        labelMap[item[valueField]][textField] = item[textField];
                    }
                }
            });
        });
        showLabels = labels;
    });

    // 将选中的数据同步至store的数据项中
    $effect(() => {
        const val = value;
        const labels: string[] = untrack(() => showLabels || []);
        const labelMap: any = {};
        labels.forEach((label: any) => {
            labelMap[label[valueField]] = label;
        });
        untrack(() => {
            store.list.forEach((item) => {
                if (multi) {
                    item._checked = val.includes(item[valueField]);
                } else {
                    item._checked = val === item[valueField];
                }
                if (item._checked && !labelMap[item[valueField]]) {
                    labels.push(item);
                }
            });
        });
        showLabels = labels;
    });

    // 点击更新setValue 并触发onChange事件
    const onOptionClick = (v: any, option: any) => {
        if (dataMap[v]) {
            if (dataMap[v].children && dataMap[v].children.length) {
                return;
            }
        }
        let arr: any[] = untrack(() => showLabels);
        if (multi) {
            let val = value;
            const index = val.indexOf(v);
            if (index > -1) {
                val.splice(index, 1);
                arr.splice(index, 1);
            } else {
                if (props.max && val.length >= props.max) {
                    props.onExceed?.();
                    return;
                }
                val = [...val];
                val.push(v);
                arr.push(option);
            }

            value = [...val];
            query = "";
            showLabels = [...arr];
            props.onchange?.(val, option);
            ctx.onFieldChange?.();
        } else {
            isClickChanging = true;
            arr = [{ ...option }];
            value = v;
            query = "";
            showLabels = [...arr];
            Promise.resolve().then(() => {
                isClickChanging = false;
            });
            open = false;
            props.onchange?.(v, option);
            ctx.onFieldChange?.();
        }
    };

    // 清空数据
    const onClear = (e: any) => {
        showLabels = [];
        if (multi) {
            value = [];
            props.onchange?.([]);
            ctx.onFieldChange?.();
        } else {
            value = "";
            query = "";
            open = false;
            props.onchange?.("");
            ctx.onFieldChange?.();
        }
    };

    // 过滤查询
    $effect(() => {
        const queryStr = query;
        // 远程查询
        if (props.remoteMethod) {
            if (isClickChanging) {
                return;
            }
            if (queryStr) {
                initLabels = [];
                clearTimeout(debounceTimer);
                debounceTimer = setTimeout(() => {
                    props.remoteMethod?.(queryStr);
                    // 保持打开状态
                    open = true;
                }, props.debounceTime || 300);
            }
        } else {
            // 本地过滤
            store.list.forEach((item) => {
                item._show = item[textField].indexOf(queryStr) > -1;
            });
            // 高亮搜索字符
            queueMicrotask(() => {
                buildNodes();
                hilightKeyword(queryStr as string);
            });
        }
    });

    /**
     * 构建搜索的节点
     * @returns
     */
    const buildNodes = () => {
        // 不支持高亮则返回
        if (!CSS.highlights) {
            return;
        }

        const treeWalker = document.createTreeWalker(filterWrap, NodeFilter.SHOW_TEXT);
        let currentNode = treeWalker.nextNode();
        while (currentNode) {
            allTextNodes.push(currentNode);
            currentNode = treeWalker.nextNode();
        }
    };

    // 高亮关键字
    const hilightKeyword = (queryStr: string) => {
        // 不支持高亮则返回
        if (!CSS.highlights) {
            return;
        }

        CSS.highlights.delete("cm-search-results");

        const str = queryStr.trim().toLowerCase();
        if (!str) {
            return;
        }

        const ranges = allTextNodes
            .map((el) => {
                return { el, text: el.textContent?.toLowerCase() };
            })
            .map(({ text, el }) => {
                const indices = [];
                let startPos = 0;
                while (text && startPos < text.length) {
                    const index = text.indexOf(str, startPos);
                    if (index === -1) break;
                    indices.push(index);
                    startPos = index + str.length;
                }
                return indices.map((index) => {
                    const range = new Range();
                    range.setStart(el, index);
                    range.setEnd(el, index + str.length);
                    return range;
                });
            });

        const searchResultsHighlight = new window.Highlight(...ranges.flat());
        CSS.highlights.set("cm-search-results", searchResultsHighlight);
    };

    // 多选场景下删除value
    const onValueClose = (item: TagConfig, e: any) => {
        if (multi) {
            const arr: any[] = showLabels;
            const val = value;
            const index = val.indexOf(item.id);
            if (index > -1) {
                val.splice(index, 1);
                arr.splice(index, 1);
            }
            value = [...val];
            showLabels = [...arr];
            props.onchange?.(val);
            ctx.onFieldChange?.();
        }
    };

    // 撤消按键，删除最后一个value
    const onDeleteLastValue = () => {
        if (multi) {
            const arr: any[] = showLabels;
            const val = value;

            if (val.length > 0) {
                val.pop();
                arr.pop();
                value = [...val];
                showLabels = [...arr];
                props.onchange?.(val);
                ctx.onFieldChange?.();
            }
        }
    };

    const displayItems = $derived.by(() => {
        return store.list.filter((item) => item._show);
    });

    const labels = $derived.by(() => {
        const arr: any[] = [];
        const showLabelsData = showLabels;
        showLabelsData.map((item) => {
            arr.push({ ...item, id: item[valueField], title: item[textField] });
        });
        if (multi) {
            return arr.length ? arr : props.emptyOption ? [{ id: "", title: props.emptyOption }] : [];
        } else {
            return arr.length ? arr[0] : props.emptyOption ? props.emptyOption : "";
        }
    });
</script>

{#snippet itemRender(item: any, index: number, itemProps: any)}
    {#if item.emptyOption}
        <EmptyOption visible data={{ label: item[textField], value: "" }} checked={value === ""} onclick={onClear} />
    {:else}
        <Option
            renderOption={props.renderOption}
            visible={item._show}
            disabled={item.disabled}
            data={item}
            checked={item._checked}
            {textField}
            {valueField}
            {...itemProps}
            onclick={(v: any) => onOptionClick(v, item)}
        />
    {/if}
{/snippet}

<div class={classList} style={props.style} bind:this={wrap}>
    <Dropdown transfer={props.transfer} fixWidth {align} disabled={props.disabled} trigger="click" bind:visible={open}>
        {#snippet menu()}
            <div class="cm-select-options-wrap">
                {#if props.header}
                    <div class="cm-select-header">
                        {#if typeof props.header === "function"}
                            {@render props.header()}
                        {:else}
                            {props.header}
                        {/if}
                    </div>
                {/if}
                <div class="cm-select-options" style={`max-height: ${props.maxHeight ? `${props.maxHeight}px` : ""}`}>
                    <ul class="cm-select-option-list" bind:this={filterWrap}>
                        {#if displayItems.length}
                            {#if props.virtual}
                                <VirtualList items={displayItems} itemEstimatedSize={30} maxHeight={props.maxHeight ?? 200} {itemRender} />
                            {:else}
                                {#each displayItems as item (item[valueField])}
                                    {#if item.emptyOption}
                                        <EmptyOption visible data={{ label: item[textField], value: "" }} checked={value === ""} onclick={onClear} />
                                    {:else}
                                        <Option
                                            renderOption={props.renderOption}
                                            visible={item._show}
                                            disabled={item.disabled}
                                            data={item}
                                            checked={item._checked}
                                            {textField}
                                            {valueField}
                                            onclick={(v: any) => onOptionClick(v, item)}
                                        />
                                    {/if}
                                {/each}
                            {/if}
                        {:else}
                            <Empty desc={props.emptyText} />
                        {/if}
                    </ul>
                    {#if props.loading}
                        <Spin class="cm-select-loading" />
                    {/if}
                </div>
                {#if props.footer}
                    <div class="cm-select-footer">
                        {#if typeof props.footer === "function"}
                            {@render props.footer()}
                        {:else}
                            {props.footer}
                        {/if}
                    </div>
                {/if}
            </div>
        {/snippet}
        {#if props.triggerRender}
            <span class="cm-select-trigger">{@render props.triggerRender?.(labels)}</span>
        {:else}
            <Value
                text={labels}
                tagRender={props.renderSelectedItem}
                {multi}
                showMax={props.showMax}
                disabled={props.disabled}
                showMore={props.showMore}
                valueClosable={props.valueClosable || props.filter}
                clearable={props.clearable}
                {onClear}
                placeholder={props.placeholder}
                prepend={props.prefix}
                size={props.size}
                onClose={onValueClose}
                bind:query
                filter={props.filter}
                {onDeleteLastValue}
            >
                {#snippet icon()}
                    <Icon icon="chevronDown" class="cm-select-cert" />
                {/snippet}
            </Value>
        {/if}
    </Dropdown>
</div>
